package com.sec.android.app.myfiles.module.local.securelocal;

import android.app.FragmentManager;
import android.content.ContentValues;
import android.content.Context;
import android.mtp.MtpConstants;
import android.provider.MediaStore;

import com.sec.android.app.myfiles.R;
import com.sec.android.app.myfiles.info.AppConstants;
import com.sec.android.app.myfiles.info.MediaFile;
import com.sec.android.app.myfiles.log.Log;
import com.sec.android.app.myfiles.module.abstraction.AbsFileOperationImp;
import com.sec.android.app.myfiles.module.abstraction.FileRecord;
import com.sec.android.app.myfiles.module.abstraction.FileRecord.StorageType;
import com.sec.android.app.myfiles.module.local.file.LocalFileRecord;
import com.sec.android.app.myfiles.module.shortcut.ShortcutFileRecord;
import com.sec.android.app.myfiles.operation.FileOperationArgs;
import com.sec.android.app.myfiles.operation.FileOperationException;
import com.sec.android.app.myfiles.operation.FileOperator;
import com.sec.android.app.myfiles.operation.FileOperator.Operation;
import com.sec.android.app.myfiles.operation.ProgressListener;
import com.sec.android.app.myfiles.provider.PrivateMyFilesProvider;
import com.sec.android.app.myfiles.util.SemFwWrapper;
import com.sec.android.app.myfiles.util.StorageMonitor;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Locale;

public class SecureLocalFileOperationImp extends AbsFileOperationImp {
    protected static final int COPY_BUFFER_SIZE = 8192;
    protected static final String FS_NOT_ENOUGH_SPACE = "ENOSPC";
    protected static final String FS_IOEXCEPTION = "EIO";
    private static final String CREATE_FOLDER_EXCEPTION_NAME = ".nomedia";
    private static final long PROGRESS_UPDATE_INTERVAL = 100;

    public SecureLocalFileOperationImp(Context context, ProgressListener listener) {
        super(context, listener);
    }

    @Override
    protected void makeScanLists(Operation op, ArrayList<String> pathList, ArrayList<String> fileList, FileOperationArgs args, int handledCount) {
        FileRecord srcRecord;
        FileRecord dstRecord = args.mDst;

        switch (op) {
            case COPY:
            case BACKUP:
                if (!StorageMonitor.startWithUsbRoot(dstRecord.getFullPath())) {
                    pathList.add(dstRecord.getFullPath());
                }
                break;
            case MOVE:
                if (handledCount > 0) {
                    addRootPath(pathList, args.mSrcList, dstRecord);
                }
                break;
            case DELETE:
                addRootPath(pathList, args.mSrcList, null);
                break;
            case RENAME:
                srcRecord = args.mSrc;
                if (srcRecord != null) {
                    if (srcRecord.isDirectory()) {
                        pathList.add(srcRecord.getFullPath());
                        pathList.add(dstRecord.getFullPath());
                    } else {
                        fileList.add(srcRecord.getFullPath());
                        fileList.add(dstRecord.getFullPath());
                    }
                    if (srcRecord.isDirectory()) {
                        String renamedFolderName = dstRecord.getFullPath();
                        if (!StorageMonitor.startWithUsbRoot(renamedFolderName)) {
                            ContentValues renamedFolderInfo = new ContentValues();
                            renamedFolderInfo.put(AppConstants.MEDIA_STORE_FILE_COLUMNS_FORMAT, MtpConstants.FORMAT_ASSOCIATION); // 12289
                            renamedFolderInfo.put(MediaStore.MediaColumns.SIZE, 0);
                            renamedFolderInfo.put(MediaStore.MediaColumns.DATA, renamedFolderName);
                            try {
                                mContext.getContentResolver().insert(MediaStore.Files.getContentUri("external"), renamedFolderInfo);
                                Log.e(this, "makeScanLists() ] " + Log.getEncodedMsg(renamedFolderName) + " is inserted in Media DB.");
                            } catch (Exception e) {
                                Log.e(this, "makeScanLists() ] " + Log.getEncodedMsg(renamedFolderName) + " is not inserted in Media DB.");
                                Log.e(this, "Exception:" + e.getMessage());
                            }
                        }
                    }
                }
                break;
            case CREATE_DIR:
                String parentPath = dstRecord.getFullPath();
                if (CREATE_FOLDER_EXCEPTION_NAME.equalsIgnoreCase(args.mCreateFolderName)) {
                    pathList.add(parentPath);
                } else {
                    String createdFolderPath = new StringBuilder(parentPath).append("/").append(args.mCreateFolderName).toString();
                    if (!StorageMonitor.startWithUsbRoot(createdFolderPath)) {
                        ContentValues createdFolderInfo = new ContentValues();
                        createdFolderInfo.put(AppConstants.MEDIA_STORE_FILE_COLUMNS_FORMAT, MtpConstants.FORMAT_ASSOCIATION); // 12289
                        createdFolderInfo.put(MediaStore.MediaColumns.SIZE, 0);
                        createdFolderInfo.put(MediaStore.MediaColumns.DATA, createdFolderPath);
                        try {
                            mContext.getContentResolver().insert(MediaStore.Files.getContentUri("external"), createdFolderInfo);
                        } catch (Exception e) {
                            Log.e(this, "makeScanLists() ] " + Log.getEncodedMsg(createdFolderPath) + " is not inserted in Media DB.");
                        }
                    }
                }
                break;
            case COMPRESS:
                fileList.add(dstRecord.getFullPath());
                break;
            case DECOMPRESS:
                File dstPath = SemFwWrapper.file(dstRecord.getFullPath());
                if (dstPath != null && dstPath.isDirectory()) {
                    pathList.add(dstRecord.getFullPath());
                }
                break;
        }
    }

    @Override
    protected boolean isSupport(FileRecord record) {
        boolean bRet = false;

        if (record.getStorageType() == StorageType.Local
                || record.getStorageType() == StorageType.FolderTree || record.getStorageType() == StorageType.Category) {
            bRet = true;
        }

        return bRet;
    }

    @Override
    protected boolean isSupportCopyMove(FileRecord src, FileRecord dst) {
        boolean bRet = false;

        if (src != null && dst != null) {
            StorageType srcType = src.getStorageType();
            StorageType dstType = dst.getStorageType();
            if (StorageType.Local.equals(srcType) && (StorageType.Local.equals(dstType) || StorageType.FolderTree.equals(dstType))) {
                bRet = true;
            } else if ((StorageType.Category.equals(srcType) && StorageType.Local.equals(dstType)) || (StorageType.Category.equals(srcType) && StorageType.Category.equals(dstType))) {
                bRet = true;
            }
        }
        return bRet;
    }

    @Override
    protected boolean _copy(FileRecord src, FileRecord dst) throws FileOperationException {
        File srcFile = SemFwWrapper.file(src.getFullPath());
        File dstFile = SemFwWrapper.file(dst.getFullPath());

        boolean bRet = false;

        try (FileInputStream inputStream = SemFwWrapper.inputStream(srcFile);
             FileOutputStream outputStream = SemFwWrapper.outputStream(dstFile)) {

            byte[] buffer = new byte[COPY_BUFFER_SIZE];
            int readBytess;
            long readTotalBytes = 0;
            long totalBytes = srcFile.length();
            long prevUpdateMillis = System.currentTimeMillis();
            long curMillis;

            mProgresslistener.onProgressChanged(null, 0, 100);

            while (!mCancel && (readBytess = inputStream.read(buffer, 0, COPY_BUFFER_SIZE)) != -1) {
                outputStream.write(buffer, 0, readBytess);
                readTotalBytes += readBytess;

                curMillis = System.currentTimeMillis();
                if (curMillis - prevUpdateMillis > PROGRESS_UPDATE_INTERVAL) {
                    mProgresslistener.onProgressChanged(null, (int) ((readTotalBytes * 100) / totalBytes), 100);
                    prevUpdateMillis = curMillis;
                }
            }

            mProgresslistener.onProgressChanged(null, 100, 100);

            if (mCancel) {
                outputStream.flush();

                if (dstFile.exists() && (totalBytes != dstFile.length())) {
                    if (dstFile.delete() == false) { // delete unfinished file
                        Log.e(this, "_copy : delete dst file is failed in cancel");
                    }
                }
            } else {
                outputStream.flush();
                bRet = true;
            }
        } catch (IOException e) {
            Log.e(this, "IOException:" + e.toString());

            if (dstFile.exists()) {
                if (dstFile.delete() == false) {
                    Log.e(this, "_copy : delete dst file is failed in exception");
                }
            }
            if (e.getMessage().contains(FS_NOT_ENOUGH_SPACE)) {
                throw new FileOperationException(mContext.getResources().getString(R.string.not_enough_memory_popup), e);
            } else if (e.getMessage().contains(FS_IOEXCEPTION)) {
                throw new FileOperationException();
            }
        }
        return bRet;
    }

    @Override
    protected boolean _move(FileRecord src, FileRecord dst) throws FileOperationException {
        boolean bRet = false;

        if (copy(src, dst)) {
            bRet = delete(src);
        }

        return bRet;
    }

    @Override
    protected boolean _delete(FileRecord record) throws FileOperationException {
        boolean bRet = false;
        File file = SemFwWrapper.file(record.getFullPath());
        if (file != null) {
            if (!file.exists()) {
                Log.e(this, "_delete() fail - file not exist");
                return false;
            }

            // return true;
            mProgresslistener.onProgressChanged(null, 0, 100);
            bRet = file.delete();
            if (bRet) {
                PrivateMyFilesProvider.refreshDb(mContext, new ShortcutFileRecord());
                mProgresslistener.onProgressChanged(null, 100, 100);
            }
        }
        return bRet;
    }

    @Override
    protected boolean _rename(FileRecord src, FileRecord dst) throws FileOperationException {
        File fsrc = SemFwWrapper.file(src.getFullPath());
        File fdst = SemFwWrapper.file(dst.getFullPath());

        boolean bRet = fsrc.renameTo(fdst);

        if (bRet) {
            dst.setFileType(MediaFile.getFileType(dst.getFullPath(), mContext));
            PrivateMyFilesProvider.refreshDb(mContext, new ShortcutFileRecord());
            PrivateMyFilesProvider.refreshDb(mContext, dst.getParent(), true);
        }

        return bRet;
    }

    @Override
    protected FileRecord _mkdir(FileRecord dst, String name) {
        FileRecord ret = null;
        String path = dst.getFullPath() + "/" + name;
        File file = SemFwWrapper.file(path);
        if (file.mkdir()) {
            ret = new LocalFileRecord(path);
        }

        return ret;
    }

    @Override
    protected ArrayList<FileRecord> _getListInDirectory(FileRecord record) {
        ArrayList<FileRecord> ret = new ArrayList<>();

        File parent = SemFwWrapper.file(record.getFullPath());
        if (parent.isDirectory()) {
            File[] files = parent.listFiles();

            if (files != null) {
                for (File file : files) {
                    if (file.getName().equals(".") || file.getName().equals("..")) {
                        continue;
                    }
                    FileRecord innerRecord = new LocalFileRecord(file.getAbsolutePath());
                    ret.add(innerRecord);
                }
            }
        }

        return ret;
    }

    @Override
    protected void _preOperation(Operation operationType) {

    }

    @Override
    protected void _postOperation(Operation operationType, FileOperationArgs args, int handledCount) {
        if (operationType != Operation.PREVIEW_COMPRESS) {
            scanForOperation(operationType, args, handledCount);
            PrivateMyFilesProvider.clearCache(mContext, null);
        }
    }

    @Override
    protected FileRecord _getUniqueFileRecord(FileRecord record) {
        int i = 1;
        int appendIndex, lengthDiff;
        String fullPath = record.getFullPath();

        String path = record.getPath() + "/" + record.getNameWithoutExt();
        String ext = record.getExtForRename();
        String name;

        File uniqueFile = SemFwWrapper.file(fullPath);

        while (uniqueFile.exists()) {
            StringBuilder sb = new StringBuilder();
            sb.append(path);
            sb.append(" (");
            sb.append(String.format(Locale.getDefault(), "%d", i++));
            sb.append(')');

            appendIndex = sb.lastIndexOf("/");
            name = sb.substring(appendIndex, sb.length());

            if (name.length() > AppConstants.MAX_INPUT_LENGTH) {
                lengthDiff = name.length() - AppConstants.MAX_INPUT_LENGTH;
                appendIndex = sb.lastIndexOf(" (");
                if (appendIndex != -1) {
                    sb = sb.delete(appendIndex - lengthDiff + 1, appendIndex);
                }
            }

            if (ext != null && !ext.isEmpty()) {
                sb.append('.');
                sb.append(ext);
            }

            fullPath = sb.toString();

            uniqueFile = SemFwWrapper.file(fullPath);
        }

        FileRecord ret = FileRecord.createFileRecord(record.getStorageType(), fullPath);
        return ret;
    }

    @Override
    protected boolean _copy(ArrayList<FileRecord> src, FileRecord dst, FragmentManager fragmentManager, boolean singleFile, boolean dupCheck,
                            FileOperator operator) throws FileOperationException {
        return false;
    }

    @Override
    protected boolean _move(ArrayList<FileRecord> src, FileRecord dst, FragmentManager fragmentManager, boolean singleFile, FileOperator operator, ArrayList<FileRecord> handledSrcList)
            throws FileOperationException {
        return false;
    }

    @Override
    protected boolean isSupportRecursiveCopyMove() {
        return true;
    }

}
